/*
 * OSBF-Lua - library for text classification
 *
 * This software is licensed to the public under the Free Software
 * Foundation's GNU GPL, version 2.  You may obtain a copy of the
 * GPL by visiting the Free Software Foundations web site at
 * www.fsf.org, and a copy is included in this distribution.
 *
 * Copyright 2005, 2006  Fidelis Assis, all rights reserved.
 *
 */

#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <inttypes.h>

#include "lua.h"

#include "lauxlib.h"
#include "lualib.h"

#include "osbflib.h"

extern int luaopen_osbf (lua_State * L);

/* configurable constants */
extern uint32_t microgroom_chain_length;
extern uint32_t microgroom_stop_after;
extern float K1, K2, K3;

/* macro to `unsign' a character */
#ifndef uchar
#define uchar(c)        ((unsigned char)(c))
#endif

/* db key names */
static char key_classes[] = "classes";
static char key_ncfs[] = "ncfs";
static char key_delimiters[] = "delimiters";

static int
osbf_config (lua_State * L)
{
  int options_set = 0;

  luaL_checktype (L, 1, LUA_TTABLE);

  lua_pushstring (L, "max_chain");
  lua_gettable (L, 1);
  if (lua_isnumber (L, -1))
    {
      microgroom_chain_length = luaL_checknumber (L, -1);
      options_set++;
    }
  lua_pop (L, 1);

  lua_pushstring (L, "stop_after");
  lua_gettable (L, 1);
  if (lua_isnumber (L, -1))
    {
      microgroom_stop_after = luaL_checknumber (L, -1);
      options_set++;
    }
  lua_pop (L, 1);

  lua_pushstring (L, "K1");
  lua_gettable (L, 1);
  if (lua_isnumber (L, -1))
    {
      K1 = luaL_checknumber (L, -1);
      options_set++;
    }
  lua_pop (L, 1);

  lua_pushstring (L, "K2");
  lua_gettable (L, 1);
  if (lua_isnumber (L, -1))
    {
      K2 = luaL_checknumber (L, -1);
      options_set++;
    }
  lua_pop (L, 1);

  lua_pushstring (L, "K3");
  lua_gettable (L, 1);
  if (lua_isnumber (L, -1))
    {
      K3 = luaL_checknumber (L, -1);
      options_set++;
    }
  lua_pop (L, 1);

  lua_pushnumber (L, (lua_Number) options_set);
  return 1;
}

static int
osbf_createdb (lua_State * L)
{
  const char *cfcname;
  uint32_t buckets;
  uint32_t minor = 0;
  char errmsg[OSBF_ERROR_MESSAGE_LEN] = { '\0' };
  int32_t num_classes;

  /* get the number of classes to create */
  num_classes = luaL_getn (L, 1);

  /* get number of buckets */
  buckets = luaL_checknumber (L, 2);

  lua_pushnil (L);		/* first key */
  while (lua_next (L, 1) != 0)
    {
      cfcname = luaL_checkstring (L, -1);
      lua_pop (L, 1);

      if (osbf_create_cfcfile (cfcname, buckets, OSBF_VERSION,
			       minor, errmsg) != EXIT_SUCCESS)
	{
	  num_classes = -1;
	  break;
	}
    }

  if (num_classes >= 0)
    lua_pushnumber (L, (lua_Number) num_classes);
  else
    lua_pushnil (L);
  lua_pushstring (L, errmsg);
  return 2;
}

/* removes all classes (files) in a database */
/* returns the number of files removed or error */
/* and the number of the last file removed */
static int
osbf_removedb (lua_State * L)
{
  const char *cfcname;
  char errmsg[OSBF_ERROR_MESSAGE_LEN] = { '\0' };
  int num_classes;
  int save_errno, removed;

  /* get the number of classes to remove */
  num_classes = luaL_getn (L, 1);
  removed = 0;
  lua_pushnil (L);		/* first key */
  while (lua_next (L, 1) != 0)
    {
      cfcname = luaL_checkstring (L, -1);
      lua_pop (L, 1);
      if (remove (cfcname) == 0)
	removed++;
      else
	{
	  save_errno = errno;
	  strncat (errmsg, cfcname, OSBF_ERROR_MESSAGE_LEN);
	  strncat (errmsg, ": ", OSBF_ERROR_MESSAGE_LEN);
	  /* append err message after file name */
	  strncat (errmsg, strerror (save_errno), OSBF_ERROR_MESSAGE_LEN);
	  break;
	}
    }

  if (errmsg[0] != '\0')
    {
      lua_pushnil (L);
      lua_pushstring (L, errmsg);
      return 2;
    }
  else
    {
      /* return the number of files deleted */
      lua_pushnumber (L, (lua_Number) removed);
      return 1;
    }
}

static int
osbf_classify (lua_State * L)
{
  const unsigned char *text;
  size_t text_len;
  const char *delimiters;	/* extra token delimiters */
  size_t delimiters_len;
  const char *classes[OSBF_MAX_CLASSES + 1];	/* set of classes */
  unsigned ncfs;		/* defines a partition with 2 subsets of the set    */
  /* "classes". The first "ncfs" classes form the  */
  /* first subset. The others form the second one.          */
  uint32_t flags = 0;		/* default value */
  double min_p_ratio;		/* min pmax/p,in ratio */
  /* classes probabilities are returned in p_classes */
  double p_classes[OSBF_MAX_CLASSES];
  char errmsg[OSBF_ERROR_MESSAGE_LEN] = { '\0' };
  unsigned i, i_pmax, num_classes;
  double p_first_subset, p_second_subset;

  /* get text pointer and text len */
  text = (unsigned char *) luaL_checklstring (L, 1, &text_len);

  /* check if the second arg is a table */
  luaL_checktype (L, 2, LUA_TTABLE);

  /* extract the class table from inside the db table */
  lua_pushstring (L, key_classes);
  lua_gettable (L, 2);

  /* extract the classes */
  /* check if the arg in the top is a table */
  luaL_checktype (L, -1, LUA_TTABLE);
  lua_pushnil (L);
  num_classes = 0;
  while (num_classes < OSBF_MAX_CLASSES && lua_next (L, -2) != 0)
    {
      classes[num_classes++] = luaL_checkstring (L, -1);
      lua_pop (L, 1);
    }
  classes[num_classes] = NULL;
  /* remove last index of the class table and the table itself */
  lua_pop (L, 1);
  if (num_classes < 1)
    return luaL_error (L, "at least one class must be given");

  /* extract the number of classes in the first subset */

  lua_pushstring (L, key_ncfs);
  lua_gettable (L, 2);
  ncfs = luaL_checknumber (L, -1);
  lua_pop (L, 1);
  if (ncfs > num_classes)
    ncfs = num_classes;

  /* extract the extra token delimiters */
  lua_pushstring (L, key_delimiters);
  lua_gettable (L, 2);
  delimiters = luaL_checklstring (L, -1, &delimiters_len);
  lua_pop (L, 1);

  /* extract flags, if any */
  flags = (uint32_t) luaL_optnumber (L, 3, 0);
  /* extract p_min_ratio if any */
  min_p_ratio = (double) luaL_optnumber (L, 4, OSBF_MIN_PMAX_PMIN_RATIO);

  /* call osbf_classify */
  if (osbf_bayes_classify (text, text_len, delimiters, classes,
			   flags, min_p_ratio, p_classes, errmsg) < 0)
    {
      lua_pushnil (L);
      lua_pushstring (L, errmsg);
    }
  else
    {
      lua_newtable (L);
      i_pmax = 0;
      p_first_subset = p_second_subset = 10 * DBL_MIN;
      for (i = 0; i < num_classes; i++)
	{
	  lua_pushnumber (L, (lua_Number) p_classes[i]);
	  lua_rawseti (L, -2, i + 1);
	  if (p_classes[i] > p_classes[i_pmax])
	    i_pmax = i;
	  if (i < ncfs)
	    p_first_subset += p_classes[i];
	  else
	    p_second_subset += p_classes[i];
	}

      /*
       * return pR, log10 of the ratio between the sum of the
       * probabilities in the first subset and the sum of the
       * probabilities in the second one.
       */
      lua_pushnumber (L,
		      (lua_Number) log10 (p_first_subset / p_second_subset));

      /* exchange array and pR positions on the stack */
      lua_insert (L, -2);

      /* return index to the class with highest probability */
      lua_pushnumber (L, (lua_Number) i_pmax + 1);
    }

  return 3;
}

static int
osbf_train (lua_State * L, int sense)
{
  const unsigned char *text;
  size_t text_len;
  const char *delimiters;	/* extra token delimiters */
  size_t delimiters_len;
  const char *classes[OSBF_MAX_CLASSES + 1];
  int num_classes;
  size_t ctbt;			/* index of the class to be trained */
  uint32_t flags = 0;		/* default value */
  char errmsg[OSBF_ERROR_MESSAGE_LEN] = { '\0' };

  /* get text pointer and text len */
  text = (unsigned char *) luaL_checklstring (L, 1, &text_len);

  /* check if the second arg is a table */
  luaL_checktype (L, 2, LUA_TTABLE);
  /* extract the class table from inside the db table */
  lua_pushstring (L, key_classes);
  lua_gettable (L, 2);

  /* extract the classes */
  /* check if the arg in the top is a table */
  luaL_checktype (L, -1, LUA_TTABLE);
  lua_pushnil (L);
  num_classes = 0;
  while (num_classes < OSBF_MAX_CLASSES && lua_next (L, -2) != 0)
    {
      classes[num_classes++] = luaL_checkstring (L, -1);
      lua_pop (L, 1);
    }
  classes[num_classes] = NULL;
  /* remove last index of the class table and the table itself */
  lua_pop (L, 1);
  if (num_classes < 1)
    return luaL_error (L, "at least one class must be given");

  /* extract the extra token delimiters */
  lua_pushstring (L, key_delimiters);
  lua_gettable (L, 2);
  delimiters = luaL_checklstring (L, -1, &delimiters_len);
  lua_pop (L, 1);

  /* get the index of the class to be trained */
  ctbt = luaL_checknumber (L, 3) - 1;

  /* get flags  */
  if (lua_isnumber (L, 4))
    flags = (uint32_t) luaL_checknumber (L, 4);

  if (osbf_bayes_learn (text, text_len, delimiters, classes,
			ctbt, sense, flags, errmsg) < 0)
    {
      lua_pushnil (L);
      lua_pushstring (L, errmsg);
      return 2;
    }
  else
    {
      lua_pushboolean (L, 1);
      return 1;
    }
}

static int
osbf_learn (lua_State * L)
{
  return osbf_train (L, 1);
}

static int
osbf_unlearn (lua_State * L)
{
  return osbf_train (L, -1);
}

static int
osbf_dump (lua_State * L)
{
#define BUCKET_BUFFER_SIZE 5000

  const char *cfcfile, *csvfile;
  FILE *fp_cfc, *fp_csv;
  OSBF_BUCKET_STRUCT buckets[BUCKET_BUFFER_SIZE];
  OSBF_HEADER_STRUCT header;
  int32_t i, size_in_buckets;
  char errormsg[OSBF_ERROR_MESSAGE_LEN];
  int error = 0;

  cfcfile = luaL_checkstring (L, 1);
  csvfile = luaL_checkstring (L, 2);

  fp_cfc = fopen (cfcfile, "r");
  if (fp_cfc != NULL)
    {
      int32_t num_buckets;

      if (1 == fread (&header, sizeof (header), 1, fp_cfc))
	{
	  size_in_buckets = header.num_buckets + header.buckets_start;
	  fp_csv = fopen (csvfile, "w");
	  if (fp_csv != NULL)
	    {
	      fseek (fp_cfc, 0, SEEK_SET);
	      while (size_in_buckets > 0)
		{
		  num_buckets = fread (buckets, sizeof (OSBF_BUCKET_STRUCT),
				       BUCKET_BUFFER_SIZE, fp_cfc);
		  if (num_buckets > 0)
		    {
		      for (i = 0; i < num_buckets; i++)
			{
			  fprintf (fp_csv,
				   "%" PRIu32 ";%" PRIu32 ";%" PRIu32 "\n",
				   buckets[i].hash, buckets[i].key,
				   buckets[i].value);
			}
		    }
		  size_in_buckets -= num_buckets;
		}
	      fclose (fp_cfc);
	      fclose (fp_csv);
	      if (size_in_buckets != 0)
		{
		  error = 1;
		  strncpy (errormsg, "Not a valid cfc file",
			   OSBF_ERROR_MESSAGE_LEN);
		}
	    }
	  else
	    {
	      error = 1;
	      strncpy (errormsg, "Can't create csv file",
		       OSBF_ERROR_MESSAGE_LEN);
	    }
	}
      else
	{
	  error = 1;
	  strncpy (errormsg, "Error reading cfc file",
		   OSBF_ERROR_MESSAGE_LEN);
	}
    }
  else
    {
      error = 1;
      strncpy (errormsg, "Can't open cfc file", OSBF_ERROR_MESSAGE_LEN);
    }

  if (error == 0)
    {
      lua_pushboolean (L, 1);
      return 1;
    }
  else
    {
      lua_pushnil (L);
      lua_pushstring (L, errormsg);
      return 2;
    }
}

static int
osbf_restore (lua_State * L)
{
  const char *cfcfile, *csvfile;
  FILE *fp_cfc, *fp_csv;
  OSBF_BUCKET_STRUCT buckets[BUCKET_BUFFER_SIZE];
  OSBF_HEADER_STRUCT *header = (OSBF_HEADER_STRUCT *) buckets;
  int32_t size_in_buckets;
  char errormsg[OSBF_ERROR_MESSAGE_LEN];
  int error = 0;

  cfcfile = luaL_checkstring (L, 1);
  csvfile = luaL_checkstring (L, 2);

  fp_csv = fopen (csvfile, "r");
  if (fp_csv != NULL)
    {
      /* read header plus an unsigned long to complete 2 buckets */
      if (5 ==
	  fscanf (fp_csv,
		  "%" SCNu32 ";%" SCNu32 ";%" SCNu32 "\n%" SCNu32 ";%" SCNu32
		  "\n", &header->version, &header->db_flags,
		  &header->buckets_start, &header->num_buckets,
		  &header->learnings))
	{
	  size_in_buckets = header->buckets_start + header->num_buckets;
	  fp_cfc = fopen (cfcfile, "w");
	  fseek (fp_csv, 0, SEEK_SET);
	  if (fp_cfc != NULL)
	    {

	      while (error == 0
		     && 3 == fscanf (fp_csv,
				     "%" SCNu32 ";%" SCNu32 ";%" SCNu32
				     "\n", (uint32_t *) & buckets[0].hash,
				     (uint32_t *) & buckets[0].key,
				     (uint32_t *) & buckets[0].value))
		{
		  if (1 ==
		      fwrite (buckets, sizeof (OSBF_BUCKET_STRUCT), 1,
			      fp_cfc))
		    {
		      size_in_buckets--;
		    }
		  else
		    {
		      error = 1;
		      strncpy (errormsg, "Error writing to cfc file",
			       OSBF_ERROR_MESSAGE_LEN);
		    }
		}
	      if (!(feof (fp_csv) && size_in_buckets == 0))
		{
		  remove (cfcfile);
		  error = 1;
		  fprintf (stderr, "Buckets left: %" PRIu32 "\n",
			   size_in_buckets);
		  strncpy (errormsg,
			   "Error reading csv or not a valid csv file",
			   OSBF_ERROR_MESSAGE_LEN);
		}
	      fclose (fp_cfc);
	      fclose (fp_csv);
	    }
	  else
	    {
	      fclose (fp_csv);
	      error = 1;
	      strncpy (errormsg, "Can't create cfc file",
		       OSBF_ERROR_MESSAGE_LEN);
	    }
	}
      else
	{
	  fclose (fp_csv);
	  remove (cfcfile);
	  error = 1;
	  strncpy (errormsg, "csv file doesn't have a valid header",
		   OSBF_ERROR_MESSAGE_LEN);
	}
    }
  else
    {
      error = 1;
      strncpy (errormsg, "Can't open csv file", OSBF_ERROR_MESSAGE_LEN);
    }

  if (error == 0)
    {
      lua_pushboolean (L, 1);
      return 1;
    }
  else
    {
      lua_pushnil (L);
      lua_pushstring (L, errormsg);
      return 2;
    }
}

static int
osbf_stats (lua_State * L)
{

  const char *cfcfile;
  FILE *fp_cfc;
  OSBF_BUCKET_STRUCT *buckets = NULL;
  OSBF_HEADER_STRUCT header;
  uint32_t i = 0, j = 0;
  char errormsg[OSBF_ERROR_MESSAGE_LEN];
  int error = 0;
  uint32_t used_buckets = 0, unreachable = 0;
  uint32_t max_chain = 0, num_chains = 0;
  uint32_t max_value = 0, first_chain_len = 0;
  uint32_t max_displacement = 0, chain_len_sum = 0;

  cfcfile = luaL_checkstring (L, 1);

  fp_cfc = fopen (cfcfile, "r");
  if (fp_cfc != NULL)
    {
      if (1 == fread (&header, sizeof (header), 1, fp_cfc))
	{
	  uint32_t chain_len = 0, value;
	  uint32_t buckets_in_buffer = 0, buffer_readings = 0;
	  uint32_t bucket_buffer_size = 0;

	  /* Check version */
	  if (header.version != OSBF_VERSION || header.db_flags != 0)
	    {
	      strncpy (errormsg, "Error: not a valid OSBF-Bayes file",
		       OSBF_ERROR_MESSAGE_LEN);
	      error = 1;
	    }
	  else
	    {
	      bucket_buffer_size =
		header.num_buckets * sizeof (OSBF_BUCKET_STRUCT);
	      buckets = malloc (bucket_buffer_size);
	      if (buckets == NULL)
		{
		  strncpy (errormsg, "Error allocating memory",
			   OSBF_ERROR_MESSAGE_LEN);
		  error = 1;
		}
	      else if (error == 0)
		{
		  fseek (fp_cfc,
			 header.buckets_start * sizeof (OSBF_BUCKET_STRUCT),
			 SEEK_SET);
		  buckets_in_buffer =
		    fread (buckets, sizeof (OSBF_BUCKET_STRUCT),
			   bucket_buffer_size, fp_cfc);
		  buffer_readings = chain_len = j = 0;
		}
	    }
	  while (error == 0 && buckets_in_buffer > 0)
	    {
	      j++;		/* number of reads */
	      if (buckets_in_buffer > 0)
		{
		  for (i = 0; i < buckets_in_buffer; i++)
		    {
		      if ((value = buckets[i].value) != 0)
			{
			  uint32_t distance, right_position;
			  uint32_t real_position, rp;

			  used_buckets++;
			  chain_len++;
			  if (value > max_value)
			    max_value = value;

			  /* calculate max displacement */
			  right_position = buckets[i].hash %
			    header.num_buckets;
			  real_position = i;
			  if (right_position <= real_position)
			    distance = real_position - right_position;
			  else
			    distance = header.num_buckets + real_position -
			      right_position;
			  if (distance > max_displacement)
			    max_displacement = distance;

			  /* check if the bucket is unreachable */
			  for (rp = right_position; rp != real_position; rp++)
			    {
			      if (rp >= header.num_buckets)
				{
				  rp = 0;
				  if (rp == real_position)
				    break;
				}
			      if (buckets[rp].value == 0)
				break;
			    }
			  if (rp != real_position)
			    {
			      unreachable++;
			    }
			}
		      else
			{
			  if (chain_len > 0)
			    {
			      if (chain_len > max_chain)
				max_chain = chain_len;
			      chain_len_sum += chain_len;
			      num_chains++;
			      chain_len = 0;
			      /* check if the first chain starts */
			      /* at the the first bucket */
			      if (j == 1 && num_chains == 1 &&
				  buckets[0].value != 0)
				first_chain_len = chain_len;
			    }
			}
		    }
		  buckets_in_buffer = fread (buckets,
					     sizeof (OSBF_BUCKET_STRUCT),
					     bucket_buffer_size, fp_cfc);
		  if (feof (fp_cfc))
		    buckets_in_buffer = 0;
		  if (buckets_in_buffer > 0)
		    buffer_readings++;
		}
	    }
	  if (error != 0)
	    {
	      if (ferror (fp_cfc))
		{
		  error = 1;
		  strncpy (errormsg, "Error reading cfc file",
			   OSBF_ERROR_MESSAGE_LEN);
		}
	    }
	  else
	    {
	      /* check if last and first chains are the same */
	      if (chain_len > 0)
		{
		  if (first_chain_len == 0)
		    num_chains++;
		  else
		    chain_len += first_chain_len;
		  chain_len_sum += chain_len;
		  if (chain_len > max_chain)
		    max_chain = chain_len;
		}
	    }
	  fclose (fp_cfc);
	}
      else
	{
	  error = 1;
	  fclose (fp_cfc);
	  strncpy (errormsg, "Error reading cfc file",
		   OSBF_ERROR_MESSAGE_LEN);
	}
    }
  else
    {
      error = 1;
      strncpy (errormsg, "Can't open cfc file", OSBF_ERROR_MESSAGE_LEN);
    }

  if (error == 0)
    {
      lua_newtable (L);

      lua_pushliteral (L, "version");
      lua_pushnumber (L, (lua_Number) header.version);
      lua_settable (L, -3);

      lua_pushliteral (L, "buckets");
      lua_pushnumber (L, (lua_Number) header.num_buckets);
      lua_settable (L, -3);

      lua_pushliteral (L, "bucket_size");
      lua_pushnumber (L, (lua_Number) sizeof (OSBF_BUCKET_STRUCT));
      lua_settable (L, -3);

      lua_pushliteral (L, "header_size");
      lua_pushnumber (L,
		      (lua_Number) header.buckets_start *
		      sizeof (OSBF_BUCKET_STRUCT));
      lua_settable (L, -3);

      lua_pushliteral (L, "learnings");
      lua_pushnumber (L, (lua_Number) header.learnings);
      lua_settable (L, -3);

      lua_pushliteral (L, "chains");
      lua_pushnumber (L, (lua_Number) num_chains);
      lua_settable (L, -3);

      lua_pushliteral (L, "max_chain");
      lua_pushnumber (L, (lua_Number) max_chain);
      lua_settable (L, -3);

      lua_pushliteral (L, "avg_chain");
      if (num_chains > 0)
	lua_pushnumber (L,
			(lua_Number) ((double) chain_len_sum / num_chains));
      else
	lua_pushnumber (L, (lua_Number) 0);
      lua_settable (L, -3);

      lua_pushliteral (L, "max_displacement");
      lua_pushnumber (L, (lua_Number) max_displacement);
      lua_settable (L, -3);

      lua_pushliteral (L, "unreachable");
      lua_pushnumber (L, (lua_Number) unreachable);
      lua_settable (L, -3);

      lua_pushliteral (L, "use");
      if (header.num_buckets > 0)
	lua_pushnumber (L,
			(lua_Number) ((double) used_buckets /
				      header.num_buckets));
      else
	lua_pushnumber (L, (lua_Number) 100);
      lua_settable (L, -3);

      return 1;
    }
  else
    {
      lua_pushnil (L);
      lua_pushstring (L, errormsg);
      return 2;
    }
}

/*
** Assumes the table is on top of the stack.
*/
static void
set_info (lua_State * L)
{
  lua_pushliteral (L, "_COPYRIGHT");
  lua_pushliteral (L, "Copyright (C) 2005, 2006 Fidelis Assis");
  lua_settable (L, -3);
  lua_pushliteral (L, "_DESCRIPTION");
  lua_pushliteral (L, "OSBF-Lua is a Lua library for text classification.");
  lua_settable (L, -3);
  lua_pushliteral (L, "_NAME");
  lua_pushliteral (L, "OSBF-Lua");
  lua_settable (L, -3);
  lua_pushliteral (L, "_VERSION");
  lua_pushliteral (L, LIB_VERSION);
  lua_settable (L, -3);
}

/* auxiliary functions */

#define MAX_DIR_SIZE 256

static int
osbf_changedir (lua_State * L)
{
  const char *newdir = luaL_checkstring (L, 1);

  if (chdir (newdir) != 0)
    {
      lua_pushboolean (L, 1);
      return 1;
    }
  else
    {
      lua_pushnil (L);
      lua_pushfstring (L, "can't change dir to '%s'\n", newdir);
      return 2;
    }
}

static int
osbf_getdir (lua_State * L)
{
  char cur_dir[MAX_DIR_SIZE + 1];

  if (getcwd (cur_dir, MAX_DIR_SIZE) != NULL)
    {
      lua_pushstring (L, cur_dir);
      return 1;
    }
  else
    {
      lua_pushnil (L);
      lua_pushstring (L, "can't get current dir");
      return 2;
    }
}


static const struct luaL_reg osbf[] = {
  {"create_db", osbf_createdb},
  {"remove_db", osbf_removedb},
  {"config", osbf_config},
  {"classify", osbf_classify},
  {"learn", osbf_learn},
  {"unlearn", osbf_unlearn},
  {"dump", osbf_dump},
  {"restore", osbf_restore},
  {"stats", osbf_stats},
  {"getdir", osbf_getdir},
  {"chdir", osbf_changedir},
  {NULL, NULL}
};


/*
** Open OSBF library
*/
int
luaopen_osbf (lua_State * L)
{
  luaL_openlib (L, "osbf", osbf, 0);
  set_info (L);
  return 1;
}
